package com.lagou.executor;

import com.lagou.config.BroundSql;
import com.lagou.pojo.Configuration;
import com.lagou.pojo.MappedStatement;
import com.lagou.utils.CamelCaseUtils;
import com.lagou.utils.GenericTokenParser;
import com.lagou.utils.ParameterMapping;
import com.lagou.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;

/**
 * @ClassName: SimpleExcutor
 * @Auther: Jerry
 * @Date: 2020/6/20 9:43
 * @Desctiption: TODO
 * @Version: 1.0
 */
public class SimpleExecutor implements Executor {

    /**
     * 存储当前线程的连接
     */
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        PreparedStatement pstmt = getPreparedStatement(configuration, mappedStatement, null == params ? null : params[0]);

        // 执行sql操作
        pstmt.execute();

        // 封装参数
        // 获取返回类型全路径
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getTypeClass(resultType);

        ArrayList<Object> objects = new ArrayList<>();

        // 获取结果集
        ResultSet resultSet = pstmt.getResultSet();
        Integer count = 0;
        while (resultSet.next()){
            // 如果返回值类型是int，直接将查询结果放入list中
            if ("java.lang.Integer".equals(resultTypeClass.getName())) {
                count = resultSet.getInt(1);
                objects.add(count);
            } else if ("java.util.Map".equals(resultTypeClass.getName())){
                ResultSetMetaData metaData = resultSet.getMetaData();
                Map<String, Object> resultMap = new LinkedHashMap<>();
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnName(i);
                    // 下划线转驼峰式
                    // 将数据封装成map返回
                    resultMap.put(CamelCaseUtils.str2hump(columnName), resultSet.getObject(columnName));
                }
                objects.add(resultMap);
            } else {
                Object obj = resultTypeClass.newInstance();
                ResultSetMetaData metaData = resultSet.getMetaData();
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnName(i);
                    // 根据反射或内省，获取到返回类型的属性并将返回结果集封装返回
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                    Object value = resultSet.getObject(columnName);
                    propertyDescriptor.getWriteMethod().invoke(obj, value);
                }
                objects.add(obj);
            }
        }

        return (List<E>) objects;
    }

    private PreparedStatement getPreparedStatement(Configuration configuration, MappedStatement mappedStatement, Object param) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
        // 1.获取数据库连接
        Connection connection = threadLocal.get();
        if (connection == null) {
            connection = configuration.getDataSource().getConnection();
            threadLocal.set(connection);
        }

        connection.setAutoCommit(false);
        // 2.获取sql语句
        /**
         *  转换sql语句：select id, username, password, sex from user where username = #{username} and password = #{password}
         *  将#{}里的参数，转换为?,并将#{}里的参数存放到broundSql对象中
         */

        String sql = mappedStatement.getSql();
        BroundSql broundSql = getBroundSql(sql);

        // 3.获取预处理对象
        PreparedStatement pstmt = connection.prepareStatement(broundSql.getSqlText());
        System.out.println("sql==================》:" + broundSql.getSqlText());
        // 4.设置参数
        List<ParameterMapping> parameterMappingList = broundSql.getParameterMappings();
        // 获取参数类型全路径
        String paramerType = mappedStatement.getParamerType();

        // 使用反射获取参数对象
        Class<?> parameterTypeClass = getTypeClass(paramerType);

        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            // 参数非空判断
            if (param != null){

                if ("java.lang.Long".equals(parameterTypeClass.getName()) ||
                        "java.lang.String".equals(parameterTypeClass.getName()) ||
                        "java.lang.Integer".equals(parameterTypeClass.getName()) ){
                    pstmt.setObject(i + 1, param);
                } else if ("java.util.Map".equals(parameterTypeClass.getName())){
                    // 将object转为map，根据key取出value并设置为参数
                    Map<String, Object> params = (HashMap<String, Object>) param;
                    String paramValue = (String) params.get(content);
                    // select id, username, password, sex from user where username = ? and password = ?
                    // param{username=admin,password=123456}
                    pstmt.setObject(i + 1, paramValue);
                } else {
                    // 获取参数属性值
                    Field declaredField = parameterTypeClass.getDeclaredField(content);
                    // 暴力访问
                    declaredField.setAccessible(true);
                    Object obj = declaredField.get(param);
                    pstmt.setObject(i + 1, obj);
                }
            }
        }
        return pstmt;
    }

    @Override
    public Integer insert(Configuration configuration, MappedStatement mappedStatement, Object... params) throws ClassNotFoundException, SQLException, IllegalAccessException, NoSuchFieldException, InstantiationException {
        return this.update(getPreparedStatement(configuration, mappedStatement, params[0]));
    }

    private Integer update(PreparedStatement preparedStatement) throws SQLException {
        PreparedStatement pstmt = preparedStatement;
        // 执行sql操作
        pstmt.execute();

        return pstmt.getUpdateCount();
    }

    @Override
    public Integer update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws ClassNotFoundException, SQLException, IllegalAccessException, NoSuchFieldException, InstantiationException {
        return this.update(getPreparedStatement(configuration, mappedStatement, params[0]));
    }


    /**
     * 根据全路径获取对象
     * @param type 可以是参数类型，也可以是返回类型
     * @return {@link Class<?>}* @throws ClassNotFoundException 类没有发现异常
     */
    private Class<?> getTypeClass(String type) throws ClassNotFoundException {
        if (type != null || "".equals(type)) {
            Class<?> aClass = Class.forName(type);
            return aClass;
        }
        return null;
    }

    private BroundSql getBroundSql(String sql) {
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", handler);
        String parse = genericTokenParser.parse(sql);
        List<ParameterMapping> parameterMappings = handler.getParameterMappings();
        return new BroundSql(parse, parameterMappings);
    }

    @Override
    public void commit() throws SQLException {
        Connection connection = threadLocal.get();
        if (connection != null && !connection.getAutoCommit()){
            connection.commit();
        }
    }

    @Override
    public void close() throws SQLException {
        Connection connection = threadLocal.get();
        if (connection != null){
            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                connection.close();
            }
        }
    }

    @Override
    public void rollback() throws SQLException {
        Connection connection = threadLocal.get();
        if (connection != null && !connection.isClosed()){
            connection.rollback();
        }
    }
}
